package com.github.zyf.ioaj.annotation.processor;

import com.github.zyf.ioaj.algorithm.AbstractAlgorithm;
import com.github.zyf.ioaj.algorithm.Function;
import com.github.zyf.ioaj.algorithm.Input;
import com.github.zyf.ioaj.algorithm.Output;
import com.github.zyf.ioaj.algorithm.evolutionary.GA;
import com.github.zyf.ioaj.algorithm.physics.GSA;
import com.github.zyf.ioaj.algorithm.physics.SAA;
import com.github.zyf.ioaj.algorithm.swarmintelligence.*;
import com.github.zyf.ioaj.constant.AlgorithmType;
import com.github.zyf.ioaj.annotation.external.Export;
import com.github.zyf.ioaj.annotation.external.IOA;
import com.github.zyf.ioaj.annotation.external.ObjectFunction;
import com.github.zyf.ioaj.exception.IOAProcessingException;
import com.github.zyf.ioaj.export.AbstractExport;
import com.github.zyf.ioaj.utils.basedata.ArrayUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * <h1>智能算法处理器</h1>
 * <p>用于处理添加了智能算法注解的类</p>
 * <p>
 * 使用方法如下：
 * <ul>
 *     <li>{@link #execute(Class)}：对指定类中的所有目标函数进行寻优</li>
 *     <li>{@link #execute(Class, String)}：对指定类指定目标函数进行寻优</li>
 *     <li>{@link #export(Class)}：对指定类中的所有目标函数进行寻优并导出报告</li>
 *     <li>{@link #export(Class, String)}：对指定类指定目标函数进行寻优并导出报告</li>
 * </ul>
 * <p>注：该处理器对参数有一定的校检，如果上下限数组长度与子变量个数不一致，将会抛出异常</p>
 *
 * @author zyf
 */
public class IOAProcessor {

    private static boolean isClassExported;
    private static ObjectFunctionNode.ExportInfo classExportInfo;

    /**
     * 目标函数相关信息的映射
     */
    private static Map<String, ObjectFunctionNode> nodeMap;

    private IOAProcessor() {
    }

    /**
     * 对指定类中的所有目标函数进行寻优
     *
     * @param clazz 指定类
     * @return Map，键为目标函数方法名（字符串），值为相应的{@link Output}
     */
    public static Map<String, Output> execute(Class<?> clazz) {
        // 先解析指定类
        parseAlgorithm(clazz);
        Map<String, Output> outputMap = new HashMap<>();
        // 如果没有异常，将遍历nodeMap
        for (Map.Entry<String, ObjectFunctionNode> entry : nodeMap.entrySet()) {
            // 获取目标函数结点
            ObjectFunctionNode node = entry.getValue();
            // 根据结点信息生成Input参数
            Input input = new Input(node.function(), node.varNum(), node.lb(), node.ub());
            // 选择优化算法类型
            AbstractAlgorithm algorithm = chooseAndReturnAlgorithm(node.algorithmType(), input);
            // 运行智能算法
            algorithm.go();
            // 获取输出量，添加到outputMap中
            outputMap.put(entry.getKey(), algorithm.getOutput());
        }
        return outputMap;
    }

    /**
     * 对指定类指定目标函数进行寻优
     *
     * @param clazz      指定类
     * @param methodName 目标函数名
     * @return 寻优结果 {@link Output}
     */
    public static Output execute(Class<?> clazz, String methodName) {
        // 先解析指定类
        parseAlgorithm(clazz);
        // 获取目标函数相关信息
        ObjectFunctionNode node = nodeMap.get(methodName);
        if (node == null)
            throw new IOAProcessingException("没有找到目标函数：" + methodName + "；原因可能是未添加目标函数注解或没有该函数");
        Input input = new Input(node.function(), node.varNum(), node.lb(), node.ub());
        // 选择优化算法类型
        AbstractAlgorithm algorithm = chooseAndReturnAlgorithm(node.algorithmType(), input);
        // 运行智能算法
        algorithm.go();
        // 返回相关结果
        return algorithm.getOutput();
    }

    /**
     * 对指定类中的所有目标函数进行寻优并导出报告
     *
     * @param clazz 指定类
     * @return Map，键为目标函数方法名（字符串），值为相应的{@link AbstractExport}
     */
    public static Map<String, AbstractExport> export(Class<?> clazz) {
        // 先解析指定类
        parseAlgorithm(clazz);
        Map<String, AbstractExport> outputMap = new HashMap<>();
        boolean hasExport = false;
        // 如果没有异常，将遍历nodeMap
        for (Map.Entry<String, ObjectFunctionNode> entry : nodeMap.entrySet()) {
            // 获取目标函数结点
            ObjectFunctionNode node = entry.getValue();
            // 根据结点信息生成Input参数
            Input input = new Input(node.function(), node.varNum(), node.lb(), node.ub());
            // 选择优化算法类型
            AbstractAlgorithm algorithm = chooseAndReturnAlgorithm(node.algorithmType(), input);

            AbstractExport abstractExport = null;
            // 按需生成报告
            if (isClassExported) {
                abstractExport = chooseAndReturnExport(algorithm, classExportInfo);
            } else {
                if (node.isExported()) {
                    hasExport = true;
                    ObjectFunctionNode.ExportInfo exportInfo = node.getExport();
                    abstractExport = chooseAndReturnExport(algorithm, exportInfo);
                }
            }
            if (!hasExport) {
                throw new IOAProcessingException("没有需要导出的报告，请检查是否添加了" + Export.class.getName() + "注解");
            }
            outputMap.put(entry.getKey(), abstractExport);
        }

        return outputMap;
    }

    /**
     * 对指定类指定目标函数进行寻优并导出报告
     *
     * @param clazz      指定类
     * @param methodName 目标函数名
     * @return 寻优报告 {@link AbstractExport}
     */
    public static AbstractExport export(Class<?> clazz, String methodName) {
        // 先解析指定类
        parseAlgorithm(clazz);
        // 获取目标函数相关信息
        ObjectFunctionNode node = nodeMap.get(methodName);
        if (node == null)
            throw new IOAProcessingException("没有找到目标函数：" + methodName + "；原因可能是未添加目标函数注解或没有该函数");
        Input input = new Input(node.function(), node.varNum(), node.lb(), node.ub());
        // 选择优化算法类型
        AbstractAlgorithm algorithm = chooseAndReturnAlgorithm(node.algorithmType(), input);

        // 按需生成报告
        if (isClassExported) {
            return chooseAndReturnExport(algorithm, classExportInfo);
        } else {
            // 如果类拥有Export才可以导出，否则抛出异常
            if (node.isExported()) {
                ObjectFunctionNode.ExportInfo exportInfo = node.getExport();
                return chooseAndReturnExport(algorithm, exportInfo);
            } else {
                throw new IOAProcessingException("没有需要导出的报告，请检查是否添加了" + Export.class.getName() + "注解");
            }
        }
    }

    /**
     * 解析算法类的内容
     *
     * @param clazz 待解析的类
     */
    public static void parseAlgorithm(Class<?> clazz) {
        // 检查参数
        if (clazz == null) throw new IOAProcessingException("参数不可为NULL");
        // 只有添加了智能算法注解的才能继续，否则抛出异常
        if (!clazz.isAnnotationPresent(IOA.class)) {
            throw new IOAProcessingException(clazz.getName() + " 未添加所需的注解：" + IOA.class.getName());
        }
        // 判断类是否有添加Export注解
        if (clazz.isAnnotationPresent(Export.class)) {
            // 告知类已经添加了注解，后面将不会扫面方法是否添加了该注解，一律按类的注解为主
            isClassExported = true;
            // 获取类Export注解
            Export export = clazz.getAnnotation(Export.class);
            // 解析类导出相关信息
            classExportInfo = new ObjectFunctionNode.ExportInfo(
                    export.author(), export.message(), export.title(), export.exportType(), export.fileType()
            );
        } else {
            isClassExported = false;
        }
        // 防止nodeMap为null，初始化一下
        if (nodeMap == null) nodeMap = new HashMap<>();
        // 防止受到缓存影响，以便及时更新
        nodeMap.clear();
        // 扫描该类的所有函数
        for (Method method : clazz.getMethods()) {
            // 添加了ObjectFunction注解的就是目标函数
            if (method.isAnnotationPresent(ObjectFunction.class)) {
                // 获取注解，从而获取注解相关参数
                ObjectFunction objectFunction = method.getAnnotation(ObjectFunction.class);
                // 反射当前函数，创建Function对象（Function对象是各智能算法的必须参数之一）
                Function function = new Function() {
                    @Override
                    public double apply(double[] params) {
                        try {
                            // invoke 函数的参数如果是基本类型，需要转换成对应的对象
                            return (double) method.invoke(clazz.newInstance(), (Object) ArrayUtils.toObject(params));
                        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    @Override
                    public String toString() {
                        return method.toString();
                    }
                };
                // 获取智能算法类型
                AlgorithmType algorithmType = objectFunction.type();
                // 获取自变量个数
                int varNum = objectFunction.varNum();
                // 获取自变量取值范围
                double[] lb = objectFunction.lb();
                double[] ub = objectFunction.ub();
                // 参数校检
                if (lb.length != varNum || ub.length != varNum) {
                    throw new IOAProcessingException("注解参数错误，自变量上下限长度与子变量个数不等");
                }
                // 封装参数
                ObjectFunctionNode objectFunctionNode;
                // 如果类已经有Export注解了，类注解为优先
                if (isClassExported) {
                    objectFunctionNode = new ObjectFunctionNode(algorithmType, varNum, lb, ub, function);
                } else {
                    // 如果类没有Export注解，判断方法是否有Export注解
                    if (method.isAnnotationPresent(Export.class)) {
                        // 获取方法Export注解
                        Export export = method.getAnnotation(Export.class);
                        // 解析导出相关设置
                        ObjectFunctionNode.ExportInfo exportInfo = new ObjectFunctionNode.ExportInfo(
                                export.author(), export.message(), export.title(), export.exportType(), export.fileType()
                        );
                        // 设置目标函数相关信息
                        objectFunctionNode = new ObjectFunctionNode(algorithmType, varNum, lb, ub, function, exportInfo);
                    } else {
                        // 如果没有注解，那么就直接设置目标函数信息
                        objectFunctionNode = new ObjectFunctionNode(algorithmType, varNum, lb, ub, function);
                    }
                }
                // 将信息添加到map中
                nodeMap.put(method.getName(), objectFunctionNode);
            }
        }
        // 如果没有目标函数，那么nodeMap内容就为空，会抛出异常
        if (nodeMap.isEmpty()) throw new IOAProcessingException("没有目标函数");
    }

    /**
     * 选择算法类型并返回一个初始化的算法器
     *
     * @param algorithmType 算法类型
     * @param input         输入量，用于初始化算法器
     * @return {@link AbstractAlgorithm}
     */
    private static AbstractAlgorithm chooseAndReturnAlgorithm(AlgorithmType algorithmType, Input input) {
        AbstractAlgorithm algorithm;
        switch (algorithmType) {
            case GA:
                algorithm = new GA(input);
                break;
            case SAA:
                algorithm = new SAA(input);
                break;
            case GSA:
                algorithm = new GSA(input);
                break;
            case ACO:
                algorithm = new ACO(input);
                break;
            case ABC:
                algorithm = new ABC(input);
                break;
            case AFSA:
                algorithm = new AFSA(input);
                break;
            case GWO:
                algorithm = new GWO(input);
                break;
            case CS:
                algorithm = new CS(input);
                break;
            case FA:
                algorithm = new FA(input);
                break;
            case PSO:
            default:
                algorithm = new PSO(input);
                break;
        }
        return algorithm;
    }

    /**
     * 选择导出类型并返回导出报告
     *
     * @param algorithm  算法运算器
     * @param exportInfo 导出设置
     * @return {@link AbstractExport}
     */
    private static AbstractExport chooseAndReturnExport(AbstractAlgorithm algorithm, ObjectFunctionNode.ExportInfo exportInfo) {
        AbstractExport abstractExport;
        switch (exportInfo.getExportType()) {
            case TRACKER:
                // tracker 会自动运行
                abstractExport = algorithm.tracker(exportInfo.getAuthor(), exportInfo.getMessage(), exportInfo.getTitle(), exportInfo.getFileType());
                break;
            case REPORT:
            default:
                algorithm.go();
                abstractExport = algorithm.report(exportInfo.getAuthor(), exportInfo.getMessage(), exportInfo.getTitle(), exportInfo.getFileType());
                break;
        }
        return abstractExport;
    }
}
